package cn.buddha.jec.mvc.advice;

import cn.buddha.jec.annotation.TongSecurityParameter;
import cn.buddha.jec.exception.TongBusinessResult;
import cn.buddha.jec.model.TongBaseResponse;
import cn.buddha.jec.model.TongContext;
import cn.buddha.jec.model.head.TongOutHead;
import cn.buddha.jec.model.head.TongPageHead;
import cn.buddha.jec.util.TongBeanUtils;
import cn.buddha.jec.util.TongUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.Map;

/**
 * @ClassName TongResponseBodyAdvice
 * @Author 友野浩二
 * @Date 2020/4/14 10:56
 * @Description
 * @Version 1.0
 *
 * <pre>
 * ResponseBodyAdvice: 是对请求响应后结果的处理，一般使用环境是处理加密的json串。
 *                     需要在controller层中调用方法上加入@RequstMapping和@ResponseBody;后者若没有，则ResponseBodyAdvice实现方法不执行。
 * 功能:
 *      1- 打印响应报文;
 *      2- 对响应报文进行加密.
 * </pre>
 */
@ControllerAdvice
@Slf4j
public class TongResponseBodyAdvice implements ResponseBodyAdvice {

    /**
     * @return boolean
     * @Author 友野浩二
     * @Description // 如果返回false，则不执行当前 advice(切面) 的业务
     * @Date 2020/4/14 11:07
     * @Param [returnType, converterType]
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        // 判断 controller的方法上是否有 @ResponseBody(响应json格式) 或者 controller类上面是不是有 @RestController(相当于@Controller + @ResponseBody)
        // 如果没有，则返回false，当前 advice 就不会继续执行了。
//        RestController restController = returnType.getDeclaringClass().getAnnotation(RestController.class);
//        return restController != null || returnType.hasMethodAnnotation(ResponseBody.class);
        return true;// 拦截所有controller
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 1- 基本条件准备
//        // controller的方法入参有RequestBody注解
//        boolean hasRequestBody = returnType.hasParameterAnnotation(RequestBody.class);
//        // controller的方法上有ResponseBody注解
//        boolean hasResponseBody = returnType.hasMethodAnnotation(ResponseBody.class);
//        // controller的类上有RestController(相当于@Controller + @ResponseBody)注解
//        RestController restController = returnType.getDeclaringClass().getAnnotation(RestController.class);

        // 2- 特殊处理
        // 条件一：controller的方法入参有@RequestBody注解；
        // 条件二：controller的方法上有@ResponseBody注解 或者 类上有@RestController注解；
        // 条件三：响应模型是否为 TongBaseResponse 的子类(开发规范要求所有的业务响应DTO需要继承TongBaseResponse)。
//        if (hasRequestBody && (restController != null || hasResponseBody) && (body instanceof TongBaseResponse)) {
        TongContext tongContext = TongContext.getInstance();
        if (tongContext != null && tongContext.isRestTongApi() && (body instanceof TongBaseResponse)) {
            TongBaseResponse tongBaseResponse = (TongBaseResponse) body;// 强转
            // 2-1 给响应报文兜底 TongOutHead(Controller返回模型的基类)
            // 如果 Restful API接口 没有按照开发规范来做(即业务Flowable没有实现AbstractFlowable],则 TongOutHead 是null,我们就需要在这里给补上
            // 这里这样做的目的就是为了对没有按照开发规范来的API接口进行兜底操作。
            if (tongBaseResponse.getTongOutHead() == null) {
                tongBaseResponse.setTongOutHead(TongBusinessResult.success());
            }

            // 2-2 打印响应日志
            TongAdviceLog.logHttpBodyData(returnType,
                    JSON.toJSONString(body, SerializerFeature.PrettyFormat, SerializerFeature.WriteDateUseDateFormat),
                    "TongBaseResponse End, URI: [{}]{}Clazz: [{}], MethodName: [{}], TongBaseResponse content is: {}");

            // 2-3 清空上下文
            tongContext.clear();

            // 2-4 响应数据加密：判断 controller 的方法上是否有 "@TongSecurityParameter 加解密注解"
            boolean hasTsp = returnType.hasMethodAnnotation(TongSecurityParameter.class);
            if (hasTsp) {
                // 获取 "加解密注解"
                TongSecurityParameter tongSecurityParameter = returnType.getMethodAnnotation(TongSecurityParameter.class);
                // 判断是否需要 "加密"
                if (tongSecurityParameter != null && tongSecurityParameter.outEncode()) {
                    log.info("开始对方法: {} 的数据进行加密操作", returnType.getMethod());
                    String tongOutHeadKey = TongUtils.lowerFistChar(TongOutHead.class.getSimpleName());
                    String tongPageHeadKey = TongUtils.lowerFistChar(TongPageHead.class.getSimpleName());

                    // 这里小生就简简单单来加密一下
                    // 将响应body体转换为map
                    Map<String, Object> map = TongBeanUtils.object2Map(tongBaseResponse);
                    map.forEach((key, value) -> {
                        if (!key.equals(tongOutHeadKey) && !key.equals(tongPageHeadKey)) {
                            // 不对 tongOutHead 和 tongPageHead 加密
                            map.put(key, value + "-我爱你");
                        }
                    });
                    TongAdviceLog.logHttpBodyData(returnType,
                            JSON.toJSONString(map, SerializerFeature.PrettyFormat, SerializerFeature.WriteDateUseDateFormat),
                            "TongBaseResponse End, URI: [{}]{}Clazz: [{}], MethodName: [{}], TongBaseResponse After encrypt content is: {}");
                    return map;
                }
            }
            return tongBaseResponse;
        } else {
            // 打印响应数据日志
            TongAdviceLog.logHttpBodyData(returnType,
                    JSON.toJSONString(body, SerializerFeature.PrettyFormat, SerializerFeature.WriteDateUseDateFormat),
                    "TongBaseResponse End, URI: [{}]{}Clazz: [{}], MethodName: [{}], TongBaseResponse content is: {}");
        }

        return body;
    }
}
